﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Tyche.Common.Util.Models.Entity;

namespace Tyche.Common.Util.Utils
{
    public class WeiXinUtil
    {
        /// <summary>
        /// 获取微信用户access_token的url
        /// </summary>
        private const string GET_USER_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={0}&secret={1}&code={2}&grant_type=authorization_code";

        /// <summary>
        /// 获取微信用户信息的url
        /// </summary>
        private const string GET_USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token={0}&openid={1}&lang=zh_CN";

        /// <summary>
        /// 获取公众号ACCESS_TOKEN的URL
        /// </summary>
        private const string GET_MP_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}";

        /// <summary>
        /// 获取JSSDK的TICKET的URL
        /// </summary>
        private const string GET_JS_SDK_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={0}&type=jsapi";

        /// <summary>
        /// 获取微信支付prepayid的URL
        /// </summary>
        private const string GET_WEIXIN_PAY_PREPAY_ID_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

        /// <summary>
        /// 私有构造函数
        /// </summary>
        private WeiXinUtil()
        {
        }

        /// <summary>
        /// 获取公众号AccessToken
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="appSecret"></param>
        /// <param name="expire"></param>
        /// <returns></returns>
        public static WeiXinMPAccessTokenEntity GetMPAccessToken(string appId, string appSecret, int expire = 3600)
        {
            var cacheKey = string.Format("Tyche.Common.Util.Utils.WeiXinUtil.GetMPAccessToken:{0}:{1}", appId, appSecret);

            return CacheUtil.Get(cacheKey, expire, () =>
            {
                var entity = HttpUtil.HttpGet(string.Format(GET_MP_ACCESS_TOKEN_URL, appId, appSecret), (response) =>
                {
                    return JsonConvert.DeserializeObject<WeiXinMPAccessTokenEntity>(response);
                });

                if (entity == null)
                {
                    throw new Exception("GET_MP_ACCESS_TOKEN_URL反序列化失败");
                }

                if (entity.ErrorCode != 0)
                {
                    throw new Exception(entity.ErrorMessage);
                }

                return entity;
            });
        }

        /// <summary>
        /// 获取微信JSSDKTicket
        /// </summary>
        /// <param name="accessToken"></param>
        /// <param name="expire"></param>
        /// <returns></returns>
        public static WeiXinJSSDKTicketEntity GetJSSDKTicket(string accessToken, int expire = 3600)
        {
            var cacheKey = string.Format("Tyche.Common.Util.Utils.WeiXinUtil.GetJSSDKTicket:{0}", accessToken);

            return CacheUtil.Get(cacheKey, expire, () =>
            {
                var entity = HttpUtil.HttpGet(string.Format(GET_JS_SDK_TICKET_URL, accessToken), (response) =>
                {
                    return JsonConvert.DeserializeObject<WeiXinJSSDKTicketEntity>(response);
                });

                if (entity == null)
                {
                    throw new Exception("GET_JS_SDK_TICKET_URL反序列化失败");
                }

                if (entity.ErrorCode != 0)
                {
                    throw new Exception(entity.ErrorMessage);
                }

                return entity;
            });
        }

        /// <summary>
        /// 获取微信用户信息
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="appSecret"></param>
        /// <param name="code"></param>
        /// <returns></returns>
        public static WeiXinUserInfoEntity GetWeiXinUserInfo(string appId, string appSecret, string code)
        {
            var entity = HttpUtil.HttpGet(string.Format(GET_USER_ACCESS_TOKEN_URL, appId, appSecret, code), (response) =>
            {
                return JsonConvert.DeserializeObject<WeiXinUserInfoEntity>(response);
            });

            if (entity == null)
            {
                throw new Exception("GET_USER_ACCESS_TOKEN_URL反序列化失败");
            }

            if (entity.ErrorCode != 0)
            {
                throw new Exception(entity.ErrorMessage);
            }

            if (entity.Scope != "snsapi_userinfo")
            {
                return entity;
            }

            var temp = HttpUtil.HttpGet(string.Format(GET_USER_INFO_URL, entity.AccessToken, entity.OpenId), (response) =>
            {
                return JsonConvert.DeserializeObject<WeiXinUserInfoEntity>(response);
            });

            if (temp == null)
            {
                throw new Exception("GET_USER_INFO_URL反序列化失败");
            }

            if (temp.ErrorCode != 0)
            {
                throw new Exception(temp.ErrorMessage);
            }

            entity.NickName = temp.NickName;
            entity.Sex = temp.Sex;
            entity.Language = temp.Language;
            entity.Province = temp.Province;
            entity.City = temp.City;
            entity.Country = temp.Country;
            entity.HeadImgUrl = temp.HeadImgUrl;
            entity.Privilege = temp.Privilege;
            entity.UnionId = temp.UnionId;

            return entity;
        }

        /// <summary>
        /// 获取微信支付前端参数
        /// </summary>
        /// <param name="orderSN"></param>
        /// <param name="openId"></param>
        /// <param name="totalFee"></param>
        /// <param name="appId"></param>
        /// <param name="mchId"></param>
        /// <param name="ip"></param>
        /// <param name="key"></param>
        /// <param name="notifyUrl"></param>
        /// <param name="body"></param>
        /// <returns></returns>
        public static WeiXinPayParameterEntity GetPayParameters(string orderSN
            , string openId
            , decimal totalFee
            , string appId
            , string mchId
            , string ip
            , string key
            , string notifyUrl
            , string body)
        {
            var timeStamp = ((int)(DateTime.Now - TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1))).TotalSeconds).ToString();
            var nonceStr = EncryptUtil.MD5(timeStamp).ToLower();

            var sortedDictionary = new SortedDictionary<string, string>();

            sortedDictionary.Add("appid", appId);
            sortedDictionary.Add("body", body);
            sortedDictionary.Add("mch_id", mchId);
            sortedDictionary.Add("nonce_str", nonceStr);
            sortedDictionary.Add("notify_url", notifyUrl);
            sortedDictionary.Add("openid", openId);
            sortedDictionary.Add("out_trade_no", orderSN);
            sortedDictionary.Add("spbill_create_ip", ip);
            sortedDictionary.Add("total_fee", Convert.ToInt32(totalFee * 100).ToString());
            sortedDictionary.Add("trade_type", "JSAPI");
            sortedDictionary.Add("sign", GeneratePaySignature(sortedDictionary, key));

            var package = string.Format("prepay_id={0}", GetPrePayId(sortedDictionary));

            var signParameters = new SortedDictionary<string, string>();

            signParameters.Add("appId", appId);
            signParameters.Add("timeStamp", timeStamp);
            signParameters.Add("nonceStr", nonceStr);
            signParameters.Add("package", package);
            signParameters.Add("signType", "MD5");

            return new WeiXinPayParameterEntity
            {
                AppId = appId,
                TimeStamp = timeStamp,
                NonceStr = nonceStr,
                Package = package,
                SignType = "MD5",
                PaySign = GeneratePaySignature(signParameters, key)
            };
        }

        /// <summary>
        /// 获取JSSDK前端参数
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="ticket"></param>
        /// <param name="url"></param>
        /// <returns></returns>
        public static WeiXinJSSDKParameterEntity GetJSSDKParameters(string appId, string ticket, string url)
        {
            var timeStamp = ((int)(DateTime.Now - TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1))).TotalSeconds);
            var nonceStr = EncryptUtil.MD5(timeStamp.ToString());
            var signature = EncryptUtil.SHA1(string.Format("jsapi_ticket={0}&noncestr={1}&timestamp={2}&url={3}", ticket, nonceStr, timeStamp, url));

            return new WeiXinJSSDKParameterEntity
            {
                AppId = appId,
                NonceStr = nonceStr,
                Timestamp = timeStamp,
                Signature = signature
            };
        }

        /// <summary>
        /// 获取微信支付prepayid
        /// </summary>
        /// <param name="sortedDictionary"></param>
        /// <returns></returns>
        private static string GetPrePayId(SortedDictionary<string, string> sortedDictionary)
        {
            var root = XDocument.Parse(HttpUtil.HttpPost(GET_WEIXIN_PAY_PREPAY_ID_URL, ConvertPayParametersToXML(sortedDictionary))).Element("xml");

            if (root.Element("return_code") == null
                || root.Element("result_code") == null
                || root.Element("return_code").Value != "SUCCESS"
                || root.Element("result_code").Value != "SUCCESS")
            {
                throw new Exception("获取微信支付prepayid失败");
            }

            return root.Element("prepay_id").Value;
        }

        /// <summary>
        /// 微信支付签名
        /// </summary>
        /// <param name="parameters"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public static string GeneratePaySignature(SortedDictionary<string, string> parameters, string key)
        {
            return EncryptUtil.MD5(string.Join("&", parameters.Select(x => string.Format("{0}={1}", x.Key, x.Value))) + string.Format("&key={0}", key)).ToUpper();
        }

        /// <summary>
        /// 参数转xml
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        private static string ConvertPayParametersToXML(SortedDictionary<string, string> parameters)
        {
            return "<xml>" + string.Join(string.Empty, parameters.Select(item => string.Format("<{0}>{1}</{2}>", item.Key, item.Value, item.Key))) + "</xml>";
        }
    }
}
